/***************************************************************************
 *   Copyleft 2006-2007 by												   *
 *	 Iwanicki Krzysztof													   *
 *   <gamma.mini.pw.edu.pl/~iwanickik>									   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/
#include "./Ellipsoid.h"
#include "../Engine3D.h"

/**************************************************************
* Constructors                                                *
***************************************************************/
Ellipsoid::Ellipsoid(cFloat fA, cFloat fB, cFloat fC, Renderer& renderer): Object3D() {
	mRenderer = renderer;
	mBoundingBox = cNull;
	mResetAdaptiveStep = false;
	mBoundingBoxVisible = true;
	mRayCastingPlaneVisible = true;
	mMFactor = 1.0f;
	mColor = Color::ToWinColor(Color(0.0f, 1.0f, 1.0f));
	mParametersMatrix.m11 = 1.0f / fA;
	mParametersMatrix.m22 = 1.0f / fB;
	mParametersMatrix.m33 = 1.0f / fC;
	mParametersMatrix.m44 = -1.0f;
	UpdateBoundingBox();
}

Ellipsoid::~Ellipsoid() {
	if (mBoundingBox) {
		delete mBoundingBox;
	}
}

/**************************************************************
* Operators                                                   *
***************************************************************/


/**************************************************************
* Other helpful functions                                     *
***************************************************************/
cVoid Ellipsoid::UpdateBoundingBox() {
	if (mBoundingBox) {
		mBoundingBox->Update(2.0f / Sqrt(mParametersMatrix.m11),
							2.0f / Sqrt(mParametersMatrix.m22),
							2.0f / Sqrt(mParametersMatrix.m33));
	} else {
		mBoundingBox = new Cuboid(2.0f / Sqrt(mParametersMatrix.m11),
							2.0f / Sqrt(mParametersMatrix.m22),
							2.0f / Sqrt(mParametersMatrix.m33));
	}
}

cVoid Ellipsoid::Render(Renderer &renderer, Matrix4D &matrix, ProjectionPlane &plane) {
	if (mResetAdaptiveStep) {
		renderer.ResetAdaptiveStep();
	}
	mResetAdaptiveStep = false;
	if (!renderer.GetAdaptiveStep()) {
		renderer.IncreaseAdaptive();
	}
	CalculateMatrix(matrix, renderer);
	if (mBoundingBoxVisible) {
		mBoundingBox->Render(renderer, matrix, plane);
	}
}

/*virtual*/ cChar* Ellipsoid::GetName() {
	return "Ellipsoid";
}

/*virtual*/ cVoid Ellipsoid::Transform(Matrix4D& matrix) {
	mResetAdaptiveStep = true;
	mBoundingBox->Transform(matrix);
	mObjectMatrix = matrix * mObjectMatrix;
}

/*virtual*/ cVoid Ellipsoid::Transform(Matrix4D& matrix, cBool bRemember) {
	mResetAdaptiveStep = true;
	mBoundingBox->Transform(matrix, bRemember);
	if (!bRemember) {
		mMatrix = matrix * mMatrix;
	} else {
		mObjectMatrix = matrix * mObjectMatrix;
	}
}

cVoid Ellipsoid::CalculateMatrix(Matrix4D& matrix, Renderer& renderer) {
	Matrix4D world = matrix * mMatrix * mObjectMatrix, worldInvertTranspose;
	Matrix4D worldInvert = world.Invert();
	worldInvertTranspose = worldInvert.Transpose();
	Matrix4D mA = worldInvertTranspose * mParametersMatrix;
	mA = mA * worldInvert;
	cPoint pBegin = mBoundingBox->GetBeginPoint(matrix);
	cPoint pDim = mBoundingBox->GetDimensions(matrix);
	pDim.x -= pBegin.x;
	pDim.y -= pBegin.y;

	cInt iAdaptiveStep = renderer.GetAdaptiveStep();
	cInt deltaX = (pDim.x / iAdaptiveStep), deltaY = (pDim.y / iAdaptiveStep);
	cInt maxDelta = deltaX > deltaY ? deltaX : deltaY;
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;

	if (maxDelta > 0) {
		cInt fX = pBegin.x;
		for (; fX < pDim.x + pBegin.x; fX += deltaX) {
			cInt fY = pBegin.y;
			for (; fY < pDim.y + pBegin.y; fY += deltaY) {
				cWinColor color = CalculateColor(fX, fY, mA);
				if (color != RGB(0, 0, 0) && deltaX && deltaY) {
					renderer.DrawRectangle(fX + iHalfWidth,
						fY + iHalfHeight, deltaX, deltaY, color, true);
				}
			}
		}
		if (deltaX > 0) {
			deltaX = deltaX >> 1;
		}
		if (deltaY > 0) {
			deltaY = deltaY >> 1;
		}
		maxDelta = maxDelta >> 1;
		if (mBoundingBoxVisible) {
			mBoundingBox->Render(renderer, matrix);
		}
		renderer.IncreaseAdaptive();
	} else {
		renderer.StopAdaptive();
	}
	if (mRayCastingPlaneVisible) {
		renderer.DrawRectangle(pBegin.x + iHalfWidth, pBegin.y + iHalfHeight,
			pDim.x, pDim.y, Color::ToWinColor(RED), false);
	}
}

cWinColor Ellipsoid::CalculateColor(cInt iX, cInt iY, Matrix4D matrix) {
	cWinColor color = Color::ToWinColor(BLACK);
	cFloat z, x = (cFloat) iX, y = (cFloat) iY;
    
    if (GetZValue(matrix, x, y, &z) > 0) {
		Vector3D n, v;
		n.mX = GetGradX(matrix, x, y, z);
		n.mY = GetGradY(matrix, x, y, z);
		n.mZ = GetGradZ(matrix, x, y, z);
		n.Normalize();
		v.mX = x; 
		v.mY = y; 
		v.mZ = z;
		v.Normalize();

		cFloat d = v.DotProduct(n);
		cFloat fPower = Power(d > 0 ? 0.0f : -d, mMFactor);
		cFloat c = (cFloat) fPower < 1.0f ? fPower : 1.0f;
		color = RGB(c * GetRValue(mColor), c * GetGValue(mColor), c * GetBValue(mColor));
		 
    }

    return color;
}

cFloat Ellipsoid::GetGradX(Matrix4D& matrix, cFloat x, cFloat y, cFloat z) {
	return matrix.m31 * z + matrix.m13 * z + 2.0f * matrix.m11 * x + matrix.m21 * y + matrix.m41 + matrix.m12 * y + matrix.m14;
}

cFloat Ellipsoid::GetGradY(Matrix4D& matrix, cFloat x, cFloat y, cFloat z) {
	return matrix.m32 * z + matrix.m23 * z + 2.0f * matrix.m22 * y + matrix.m21 * x + matrix.m42 + matrix.m12 * x + matrix.m24;
}

cFloat Ellipsoid::GetGradZ(Matrix4D& matrix, cFloat x, cFloat y, cFloat z) {
	return matrix.m31 * x + matrix.m13 * x + 2.0f * matrix.m33 * z + matrix.m32 * y + matrix.m43 + matrix.m23 * y + matrix.m34;
}

cInt Ellipsoid::GetZValue(Matrix4D& matrix, cFloat x, cFloat y, cFloat* z) {
	*z = 0;
#define GETR	1.0f

	cFloat a = matrix.m33;
    cFloat b = ((matrix.m31 + matrix.m13) * x + (matrix.m32 + matrix.m23) * y + (matrix.m43 + matrix.m34));
    cFloat c = (matrix.m11 * x * x + matrix.m22 * y * y + (matrix.m41 + matrix.m14) * x + (matrix.m42 + matrix.m24) * y + (matrix.m21 + matrix.m12) * x * y) + matrix.m44;
	cFloat delta = Sqr(b) - 4 * a * c;

	if (delta < 0 || (a < 0.00001f && a > -0.00001f)) {
		return -1;
	} else if (delta == 0) {		
		*z = -b / (2.0f * a);
		return 0;
	} else {
		cFloat sqrtDelta = Sqrt(delta);
		cFloat z1 = (-b - sqrtDelta) / (2.0f * a);
		cFloat z2 = (-b + sqrtDelta) / (2.0f * a);

		if (z1 > GETR && z2 > GETR) {
			*z = (z1 < z2) ? z1 : z2;
		} else if (z1 > GETR && z2 <= GETR) {
			*z = z1;
		} else if (z2 > GETR && z1 <= GETR) {
			*z = z2;
		} else if (z1 <= GETR && z2 <= GETR) {
			return -1;
		}

		return 1;
	}
}

cVoid Ellipsoid::ChangeMFactor(cFloat mFactor) {
	if (mMFactor + mFactor < 0.0f || mMFactor + mFactor > 1000.0f) {
		return;
	}
	mResetAdaptiveStep = true;
	mMFactor += mFactor;
}

cVoid Ellipsoid::ShowHideBoundingBox() {
	mBoundingBoxVisible = !mBoundingBoxVisible;
}

cVoid Ellipsoid::ShowHideRayCastingPlane() {
	mRayCastingPlaneVisible = !mRayCastingPlaneVisible;
}

cVoid Ellipsoid::ChangeA(cFloat fA) {
	if (fA < 100.0f) {
		return;
	}
	mResetAdaptiveStep = true;
	mParametersMatrix.m11 = 1.0f / fA;
	UpdateBoundingBox();
}

cVoid Ellipsoid::ChangeB(cFloat fB) {
	if (fB < 100.0f) {
		return;
	}
	mResetAdaptiveStep = true;
	mParametersMatrix.m22 = 1.0f / fB;
	UpdateBoundingBox();
}

cVoid Ellipsoid::ChangeC(cFloat fC) {
	if (fC < 100.0f) {
		return;
	}
	mResetAdaptiveStep = true;
	mParametersMatrix.m33 = 1.0f / fC;
	UpdateBoundingBox();
}

/*virtual*/cVoid Ellipsoid::DisplayInfo(Renderer& renderer) {
	cChar pcBuff [MAX_BUF_SIZE];
	cInt iLineNr = 0;

	sprintf_s(pcBuff, "Ellipsoid Info:");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, mColor);
	sprintf_s(pcBuff, "A : %6.2f", 0.10f / mParametersMatrix.m11);
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "B : %6.2f", 0.10f / mParametersMatrix.m22);
	renderer.DrawString( H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "C : %6.2f", 0.10f / mParametersMatrix.m33);
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "M : %6.2f", mMFactor);
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	iLineNr++;
	sprintf_s(pcBuff, "[Ctrl/Alt + A] - Increase/Decrease A");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl/Alt + B] - Increase/Decrease B");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl/Alt + C] - Increase/Decrease C");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[N/M] - Increase/Decrease M");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl + P] - Show/Hide Bounding Box");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Alt + P] - Show/Hide Ray Casting Plane");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
}